]> dgit.raspbian.org Git - dovecot.git/commitdiff
[PATCH 2/3] doveadm: compress-connect - Don't hang when server rejects COMPRESS
authorTimo Sirainen <timo.sirainen@open-xchange.com>
Mon, 6 Jul 2026 07:17:42 +0000 (07:17 +0000)
committerNoah Meyerhans <noahm@debian.org>
Wed, 16 Sep 2026 19:06:35 +0000 (15:06 -0400)
After sending a COMPRESS command the client stopped reading stdin and
waited for the server's reply, but it only resumed on an "OK Begin
compression" reply. If the server rejected COMPRESS (e.g. a NO reply for
an unsupported mechanism), compress_waiting was never cleared: the client
waited forever for an OK that never came while the server waited for the
next command, deadlocking the session.

Remember the COMPRESS command tag and treat any tagged reply to it that
isn't the compression-start reply as a rejection, resuming the pipelined
input.

Gbp-Pq: Name 0002-doveadm-compress-connect-Don-t-hang-when-server-reje.patch

src/doveadm/doveadm-compress.c

index 616d29fa3ae00973c6b5b346b1227782fc6c538a..1a2b556b07482396f443eb155b7d129808b06f09 100644 (file)
@@ -20,6 +20,7 @@ enum server_input_line_type {
        SERVER_INPUT_LINE_TYPE_DEFAULT,
        SERVER_INPUT_LINE_TYPE_STARTTLS,
        SERVER_INPUT_LINE_TYPE_COMPRESS,
+       SERVER_INPUT_LINE_TYPE_COMPRESS_FAILED,
 };
 
 struct client {
@@ -32,6 +33,7 @@ struct client {
        struct ssl_iostream *ssl_iostream;
        const struct compression_handler *handler;
        char *algorithm;
+       char *compress_tag;
        bool tls;
        bool compressed;
        bool compress_waiting;
@@ -111,17 +113,23 @@ cmd_dump_imap_compress(struct doveadm_cmd_context *cctx,
 static bool
 client_input_get_compress_algorithm(struct client *client, const char *line)
 {
-       const char *algorithm;
+       const char *algorithm, *tag = line;
+       size_t tag_len;
 
        /* skip tag */
        while (*line != ' ' && *line != '\0')
                line++;
+       tag_len = line - tag;
        if (!str_begins_icase(line, " COMPRESS ", &algorithm))
                return FALSE;
 
        if (compression_lookup_handler(t_str_lcase(algorithm),
                                       &client->handler) <= 0)
                i_fatal("Unsupported compression mechanism: %s", algorithm);
+       /* Remember the tag so we can tell whether the server accepted or
+          rejected this COMPRESS command. */
+       i_free(client->compress_tag);
+       client->compress_tag = i_strndup(tag, tag_len);
        return TRUE;
 }
 
@@ -190,6 +198,13 @@ static bool server_input_is_compress_reply(const char *line)
        return str_begins_with(line, " OK Begin compression");
 }
 
+static bool server_input_is_reply_to_tag(const char *line, const char *tag)
+{
+       size_t tag_len = strlen(tag);
+
+       return strncmp(line, tag, tag_len) == 0 && line[tag_len] == ' ';
+}
+
 static enum server_input_line_type
 server_input_line_type(struct client *client, const char *line)
 {
@@ -197,6 +212,13 @@ server_input_line_type(struct client *client, const char *line)
                return SERVER_INPUT_LINE_TYPE_STARTTLS;
        if (!client->compressed && server_input_is_compress_reply(line))
                return SERVER_INPUT_LINE_TYPE_COMPRESS;
+       /* The server can reject COMPRESS (e.g. an unsupported mechanism). If we
+          are waiting for its reply, a tagged reply that isn't the "OK Begin
+          compression" above means the command failed - resume sending the
+          pipelined input instead of waiting forever. */
+       if (client->compress_waiting && client->compress_tag != NULL &&
+           server_input_is_reply_to_tag(line, client->compress_tag))
+               return SERVER_INPUT_LINE_TYPE_COMPRESS_FAILED;
        return SERVER_INPUT_LINE_TYPE_DEFAULT;
 }
 
@@ -269,9 +291,18 @@ static void server_input(struct client *client)
                        client->output = output;
                        client->compressed = TRUE;
                        client->compress_waiting = FALSE;
+                       i_free_and_null(client->compress_tag);
                        i_stream_set_input_pending(client->stdin_input, TRUE);
                        break;
                }
+               case SERVER_INPUT_LINE_TYPE_COMPRESS_FAILED:
+                       /* Server rejected COMPRESS. Resume sending input. */
+                       e_info(client->event, "<Compression rejected>");
+                       client->handler = NULL;
+                       client->compress_waiting = FALSE;
+                       i_free_and_null(client->compress_tag);
+                       i_stream_set_input_pending(client->stdin_input, TRUE);
+                       break;
                case SERVER_INPUT_LINE_TYPE_DEFAULT:
                        break;
                }
@@ -329,6 +360,7 @@ static void cmd_compress_connect(struct doveadm_cmd_context *cctx)
        ssl_iostream_destroy(&client.ssl_iostream);
        i_stream_unref(&client.stdin_input);
        i_stream_unref(&client.input);
+       i_free(client.compress_tag);
        o_stream_unref(&client.output);
        event_unref(&client.event);
        if (close(fd) < 0)